import java.util.*;
import net.dclausen.microfloat.*;

/* Workhorse: the AST node. The recursive interpreter is implemented here, exec().
 */

public class Node {
    int type = 0; // type of node
    int line, col; // location node was instantiated
    Node child[]; // children of the tree node
    Object val; // value of the node, typically NUMBERs and IDentifier names
    
    public Node() {
        line = 0; col = 0;
    }
    
    /** new Node of type t */
    public Node(int t, Integer lin, Integer co) {
        type = t;
        line = lin.intValue();
        col = co.intValue();
        
    }
    
    /** new Node of type t, with value o1 */
    public Node(int t, Object o1, Integer lin, Integer co) {
        child = new Node[1];
        type = t;
        line = lin.intValue(); col = co.intValue();
        switch (type) {
            case Parser.NUMBER:
            case Parser.FILENUMBER:
            case Parser.CALL:
            case Parser.ID:
            case Parser.STRING:
                /* ID name, NUMBER value, procedure name, file number, string value
                 * are kept in object val
                 */
                val = o1;
                break;
            case Parser.ENTRYP:
                // entry point of the program
                child[0] = (Node)o1;
                // insert the weird procedure name __ENTRYP__ in symtable
                WorkSpace.putProc("__ENTRYP__", this);
                break;
            default:
                child[0] = (Node) o1;
                break;
        }
    }
    
    /** new Node of type t, with 2 children */
    public Node(int t, Object o1, Object o2, Integer lin, Integer co) {
        type = t;
        line = lin.intValue(); col = co.intValue();
        switch (type) {
            case Parser.CALLP:
            case Parser.ASSIGN: // id = exp
            case Parser.INPUT:
            case Parser.INPUTF:
            case Parser.PRINTF:
                val = o1; // ID name, procedure name
                if (o2 != null){
                    child = new Node[1];
                    child[0] = (Node) o2; // exp for ASSIGN, string/prompt for PRINT/INPUT
                }
                break;
            case Parser.ERROR:
                child = new Node[1];
                child[0] = new ErrorNode(o1, o2);
                break;
            case Parser.OPEN:
                child = new Node[1];
                val = o2; // file number
                child[0] = (Node) o1; // file name
                break;
            default:
                child = new Node[2];
                child[0] = (Node) o1;
                child[1] = (Node) o2;
                break;
                
        }
    }
    
    /** new Node of type t: operators, b: type of operator */
    public Node(int t, Object o1, Object o2, int b, Integer lin, Integer co) {
        type = t;
        line = lin.intValue(); col = co.intValue();
        val = new Integer(b);
        child = new Node[2];
        child[0] = (Node) o1;
        child[1] = (Node) o2;
    }
    
    /** new Node of type t, with 3 children */
    public Node(int t, Object o1, Object o2, Object o3, Integer lin, Integer co) {
        type = t;
        line = lin.intValue(); col = co.intValue();
        switch(type) {
            case Parser.INPUTA: // array input
                child = new Node[2];
                if(o2 != null) { child[0] = (Node) o2; } // string
                child[1] = (Node) o3; // index
                val = o1; // id
                break;
            case Parser.ARRASSIGN: // id = exp
                child = new Node[2];
                child[0] = (Node) o2; // param_list
                child[1] = (Node) o3; // exp
                val = o1; // id
                break;
            case Parser.SUB:
                child = new Node[2];
                val = o1; // proc name as string
                child[0] = (Node) o2; // statements
                if(o3 != null) child[1] = (Node) o3; // param_list
                WorkSpace.putProc(val, this); // insert in symtable
                break;
            default:
                child = new Node[3];
                child[0] = (Node) o1;
                child[1] = (Node) o2;
                child[2] = (Node) o3;
                break;
        }
    }
    
    /** new Node of type t, with 4 children: FOR loop */
    public Node(int t, Object o2, Object o4, Object o6, Object o8, Integer lin, Integer co) {
        type = t;
        line = lin.intValue(); col = co.intValue();
        switch(type) {
            default:
                //ID exp exp stmts $2, $4, $6, $8)
                child = new Node[3];
                val = o2; // loop-counter
                child[0] = (Node) o4;  // from
                child[1] = (Node) o6; // to
                child[2] = (Node) o8; // loop-body
                break;
        }
    }
    
    /** new Node of type t: FOR STEP */
    public Node(int t, Object o2, Object o4, Object o6, Object o8, Object o10, Integer lin, Integer co) {
        //ID  exp exp   exp    stmts
        //2  4     6     8        10
        // $$ = new Node(Parser.STEP, $2, $4, $6, $8, $10);
        child = new Node[4];
        type = t;
        line = lin.intValue(); col = co.intValue();
        val = o2; // loop-counter
        child[0] = (Node) o4; // initial
        child[1] = (Node) o6; // to
        child[2] = (Node) o8; // step
        child[3] = (Node) o10; // loop-body
    }
    
    /** The recursive interpreter */
    public Value exec() throws Exception {
//        System.out.println("exec " + type);
        long temp = 0, r01 = 0, r02 = 0; // utilities
        Value rval = null, lval = null;
        Value returnVal = new Value(); // return value
        try {
            switch (type) {
                case Parser.ENTRYP:
                    // execute the entry point
                    child[0].exec();
                    // then reset the flags
                    WorkSpace.breakFlag = false;
                    WorkSpace.contFlag = false;
                    break;
                case Parser.CR: // NOP
                    break;
                case Parser.MINUS: // a minus operator
                    lval = child[0].exec();
                    rval = child[1].exec();
                    returnVal.dVal = MicroDouble.sub(lval.dVal, rval.dVal);
                    returnVal.type = Value.NUM;
                    break;
                case Parser.NEGATE:
                    lval = child[0].exec();
                    returnVal.dVal = MicroDouble.negate(lval.dVal);
                    returnVal.type = Value.NUM;
                    break;
                case Parser.PLUS:
                    // add two numbers or concatenate two strings
                    lval = child[0].exec();
                    rval = child[1].exec();
                    if (lval.type == Value.TXT && rval.type == Value.TXT) {
                        returnVal.sVal = lval.sVal + rval.sVal;
                        returnVal.type = Value.TXT;
                    } else if (lval.type == Value.TXT && rval.type == Value.NUM) {
                        returnVal.sVal = lval.sVal + MicroDouble.toString(rval.dVal);
                        returnVal.type = Value.TXT;
                    } else if (lval.type == Value.NUM && rval.type == Value.TXT) {
                        returnVal.sVal = rval.sVal + MicroDouble.toString(lval.dVal);
                        returnVal.type = Value.TXT;
                    } else {
                        returnVal.dVal = MicroDouble.add(lval.dVal, rval.dVal);
                        returnVal.type = Value.NUM;
                    }
                    break;
                case Parser.MUL:
                    lval = child[0].exec();
                    rval = child[1].exec();
                    returnVal.dVal = MicroDouble.mul(lval.dVal, rval.dVal);
                    returnVal.type = Value.NUM;
                    break;
                case Parser.DIV:
                    lval = child[0].exec();
                    rval = child[1].exec();
                    returnVal.dVal = MicroDouble.div(lval.dVal, rval.dVal);
                    returnVal.type = Value.NUM;
                    break;
                case Parser.POWER:
                    lval = child[0].exec();
                    rval = child[1].exec();
                    returnVal.dVal = MicroDouble.pow(lval.dVal, rval.dVal);
                    returnVal.type = Value.NUM;
                    break;
                case Parser.FACT:
                    lval = child[0].exec();
                    returnVal.dVal = MicroDouble.factorial(lval.dVal);
                    returnVal.type = Value.NUM;
                    break;
                case Parser.TRUE:
                    returnVal.bVal = true;
                    returnVal.type = Value.BOL;
                    break;
                case Parser.FALSE:
                    returnVal.bVal = false;
                    returnVal.type = Value.BOL;
                    break;
                case Parser.FILENUMBER:
                    returnVal.sVal = val.toString();
                    returnVal.type = Value.TXT;
                    break;
                case Parser.NUMBER:
                    returnVal.dVal = MicroDouble.parseDouble(val.toString());
                    returnVal.type = Value.NUM;
                    break;
                case Parser.ID:
                    //* lookup in variable space otherwise return 0
                    int code = 0;
                    if (val.toString().equalsIgnoreCase("e") == true) {
                        // check for E, PI
                        returnVal.dVal = MicroDouble.E;
                        returnVal.type = Value.NUM;
                    } else if (val.toString().equalsIgnoreCase("pi") == true) {
                        returnVal.dVal = MicroDouble.PI;
                        returnVal.type = Value.NUM;
                    } else if ( (code = WorkSpace.libraryContains(val)) != 0) {
                        /* check if library procedure save the type of current node in ttemp
                         * change the type to library procedute code (from lookup)
                         */
                        int ttemp = this.type;
                        this.type = code; code = 0;
                        returnVal = WorkSpace.execLib(this);
                        this.type = ttemp; // retrieve the type again
                    } else if (WorkSpace.getProc(val) != null) {
                        // if procedure, execute, and get return value
                        ((Node)WorkSpace.getProc(val)).exec();
                        returnVal = WorkSpace.getVar(val);
                    } else if ((returnVal = WorkSpace.getVar(val)) != null) {
                        // get the varaible value for workspace
                    } else {
                        /* else value = null. actually this point is never reached since the
                         * WorkSpace.getVar() will alwayz return a value (or zero if no value)
                         */
                        returnVal = null;
                    }
                    break;
                case Parser.BOOL:
                    int type = ((Integer)val).intValue();
                    switch(type) {
                        case Parser.AND:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = lval.bVal && rval.bVal;
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.OR:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = lval.bVal || rval.bVal;
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.XOR:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = (!lval.bVal && rval.bVal) || (lval.bVal && !rval.bVal);
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.XNOR:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = (!lval.bVal && !rval.bVal) || (lval.bVal && rval.bVal);
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.NOT:
                            lval = child[0].exec();
                            returnVal.bVal = !lval.bVal;
                            returnVal.type = Value.BOL;
                            break;
                    }
                    break;
                case Parser.PREDICATE:
                    type = ((Integer)val).intValue();
                    switch(type) {
                        case Parser.EQ: // test for equality
                            lval = child[0].exec();
                            rval = child[1].exec();
                            if (lval.type == Value.TXT && rval.type == Value.TXT) {
                                returnVal.bVal = lval.sVal.equalsIgnoreCase(rval.sVal);
                            } else {
                                returnVal.bVal = MicroDouble.eq(lval.dVal, rval.dVal);
                            }
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.NE:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            if (lval.type == Value.TXT && rval.type == Value.TXT) {
                                returnVal.bVal = ! lval.sVal.equalsIgnoreCase(rval.sVal);
                            } else {
                                returnVal.bVal = ! MicroDouble.eq(lval.dVal, rval.dVal);
                            }
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.GT:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = MicroDouble.gt(lval.dVal, rval.dVal);
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.LT:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = MicroDouble.lt(lval.dVal, rval.dVal);
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.GE:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = MicroDouble.ge(lval.dVal, rval.dVal);
                            returnVal.type = Value.BOL;
                            break;
                        case Parser.LE:
                            lval = child[0].exec();
                            rval = child[1].exec();
                            returnVal.bVal = MicroDouble.le(lval.dVal, rval.dVal);
                            returnVal.type = Value.BOL;
                            break;
                    }
                    break;
                case Parser.SELECT:
                    // push test-value on the stack
                    WorkSpace.stack.push(child[0].exec());
                    child[1].exec();
                    WorkSpace.stack.pop(); // clean-up
                    break;
                case Parser.CASELSE:
                    // execute the else part
                    child[0].exec();
                    break;
                case Parser.CASEBLK:
                    lval = child[0].exec();
                    rval = (Value)WorkSpace.stack.peek();
                    // compare test-value (top of the stack) and case-condition (lval)
                    if (lval.type == Value.TXT && rval.type == Value.TXT) {
                        // text-wise
                        if(lval.sVal.equalsIgnoreCase(rval.sVal) == true) {
                            return child[1].exec();
                        } else if (val != null) {
                            ((Node)val).exec();
                        }
                    } else {
                        // number-wise
                        if( MicroDouble.eq(child[0].exec().dVal, ((Value)WorkSpace.stack.peek()).dVal) == true) {
                         /* optimization tweak: return immediately on
                          * true case statment */
                            return child[1].exec();
                        } else if (val != null) {
                            // test another (recursively)
                            ((Node)val).exec();
                        }
                    }
                    break;

                case Parser.ASSIGN: // insert a value in symbol table
                    WorkSpace.putVar(val, child[0].exec());
                    break;

                case Parser.ARRASSIGN: // insert a value in symbol table
                    Value v1 = child[0].exec();
                    WorkSpace.putArrayElement(val, v1, child[1].exec());
                    break;

                case Parser.STRING:
                    returnVal.sVal = val.toString();
                    returnVal.type = Value.TXT;
                    break;

                case Parser.STRINGEXP:
                    lval = child[0].exec();
                    rval = child[1].exec();
                    returnVal.sVal =  lval.toString() + rval.toString();
                    returnVal.type = Value.TXT;
                    break;

                case Parser.STRINGEXPT:
                    // string expression with a TAB
                    lval = child[0].exec();
                    rval = child[1].exec();
                    returnVal.sVal =  lval.toString() + Parser.TAB + rval.toString();
                    returnVal.type = Value.TXT;
                    break;

                case Parser.PRINT:
                    // print on active shown screen
                    if (child[0] != null) {
                        if (WorkSpace.parent.gScreen.isShown() == true) {
                            WorkSpace.printG(child[0].exec().toString());
                        } else {
                            WorkSpace.print(child[0].exec().toString());
                        }
                    }
                    break;

                case Parser.PRINTF:
                    WorkSpace.printToFile(val.toString(), child[0].exec().toString());
                    break;

                case Parser.INPUTF:
                    WorkSpace.inputFromFile(val.toString(), child[0].val.toString());
                    break;

                case Parser.INPUT:
                case Parser.INPUTA:
                    String label = "?";
                    WorkSpace.inReady = false;
                    // set the prompt
                    if (child != null && child[0] != null) {
                        label = child[0].exec().sVal + label; }
                    /*if (WorkSpace.parent.gScreen.isShown() == true) {
                        WorkSpace.printG(label);
                    } else {*/
                    WorkSpace.display.setCurrent(WorkSpace.parent.get_outForm());
                    WorkSpace.inTextField.setLabel(label);
                    //}
                    while (WorkSpace.inReady == false) {} // wait for input to be ready
                    /* input ready. store it.
                     * if TEXT or variable name ends with $: store text
                     * if NUMBER: store it. or if array store array element
                     */
                    if (val.toString().endsWith("$") == true) {
                        if(this.type == Parser.INPUTA) { WorkSpace.putArrayElement(val, child[1].exec(), new Value(WorkSpace.inTextField.getString()));
                        } else {
                            WorkSpace.putVar(val, new Value(WorkSpace.inTextField.getString())); }
                    } else {
                        try {
                            if(this.type == Parser.INPUTA) { WorkSpace.putArrayElement(val, child[1].exec(), new Value(MicroDouble.parseDouble(WorkSpace.inTextField.getString())));
                            } else {
                                WorkSpace.putVar(val, new Value(MicroDouble.parseDouble(WorkSpace.inTextField.getString()))); }
                        } catch (NumberFormatException ee) {
                            if(this.type == Parser.INPUTA) { WorkSpace.putArrayElement(val, child[1].exec(), new Value(WorkSpace.inTextField.getString()));
                            } else {
                                WorkSpace.putVar(val, new Value(WorkSpace.inTextField.getString())); }
                        }
                    }
                    // reset the prompt, input-bar
                    WorkSpace.inTextField.setLabel("in");
                    WorkSpace.inTextField.setString("");
                    break;

                case Parser.IF:
                    // execute the condition
                    if (child[0].exec().bVal == true) { // if true
                        child[1].exec(); // exec
                    } else {
                        return null;
                    }
                    break;

                case Parser.ELSE: // if-else
                    // execute the condition
                    if (child[0].exec().bVal == true) { // if true
                        child[1].exec(); // exec
                    } else { // else
                        child[2].exec(); // exec second
                    }
                    break;

                case Parser.BREAK:
                    WorkSpace.breakFlag = true;
                    break;

                case Parser.CONTINUE:
                    WorkSpace.contFlag = true;
                    break;

                case Parser.WEND:
                    while (child[0].exec().bVal == true) { // while true
                        child[1].exec(); // exec
                        // if CONTINUE or EXIT are fired, update the flags
                        if(WorkSpace.contFlag == true) { WorkSpace.contFlag = false; }
                        // if EXIT, return immediately
                        if(WorkSpace.breakFlag == true) { WorkSpace.breakFlag = false; return null; }
                    }
                    break;

                case Parser.DO:
                    do {
                        child[1].exec(); // exec
                        if(WorkSpace.contFlag == true) { WorkSpace.contFlag = false; }
                        if(WorkSpace.breakFlag == true) { WorkSpace.breakFlag = false; return null; }
                    } while (child[0].exec().bVal == true);
                    break;

                case Parser.FOR:
                    for(long i = child[0].exec().dVal; MicroDouble.le(i, child[1].exec().dVal) == true ; i = MicroDouble.add(i, MicroDouble.ONE)) {
                        WorkSpace.putVar(val, new Value(i));
                        child[2].exec(); // exec
                        if(WorkSpace.contFlag == true) { WorkSpace.contFlag = false; }
                        if(WorkSpace.breakFlag == true) { WorkSpace.breakFlag = false; return null; }
                    }
                    break;

                case Parser.STEP:
                    /* WEAKEST PART: MEMORY EATING RECURSIVE CALLS
                     * NEEDS REFINEMENTS.
                     */
                    // compute the step
                    temp = MicroDouble.div(MicroDouble.abs(child[2].exec().dVal), child[2].exec().dVal);
                    for(long i = child[0].exec().dVal;
                    (MicroDouble.lt(child[1].exec().dVal, MicroDouble.ZERO) == true) ?
                        MicroDouble.ge(i, child[1].exec().dVal) :
                        MicroDouble.ge(MicroDouble.mul(MicroDouble.sub(child[1].exec().dVal, i), temp), MicroDouble.ZERO) == true ;
                    i = MicroDouble.add(i, child[2].exec().dVal)) {
                        WorkSpace.putVar(val, new Value(i)); // update loop counter
                        child[3].exec(); // exec
                        if(WorkSpace.contFlag == true) { WorkSpace.contFlag = false; }
                        if(WorkSpace.breakFlag == true) { WorkSpace.breakFlag = false; return null; }
                    }
                    break;

                case Parser.STMTS:
                    /* a group of statments. actually, a linked-list of statmetns. now traverse
                     * till reaching end (child[0] = null)
                     */
                    child[1].exec();
                    if(WorkSpace.contFlag == true) { return null; }
                    if(WorkSpace.breakFlag == true) { return null; }
                    if (child[0] != null) {
                        child[0].exec();
                    }
                    break;

                case Parser.PARAM:
                    Node root = this;
                    WorkSpace.paramNumber = 0;
                    // execute the parameter, and push the value onto the stack
                    WorkSpace.stack.push(root.child[0].exec());
                    WorkSpace.paramNumber++; // update parameter number counter
                    while(root.child[1] != null) {
                        // traverse the linked list
                        root = root.child[1];
                        WorkSpace.stack.push(root.child[0].exec());
                        WorkSpace.paramNumber++;
                    }
                    break;

                case Parser.CALLP:
                    // parameter call of subroutine/function
                    code = 0;
                    Node proc = null;
                    Vector arr = null;
                    if ( (code = WorkSpace.libraryContains(val)) != 0) {
                        // if library procedure execute
                        int ttemp = this.type;
                        this.type = code; code = 0;
                        returnVal = WorkSpace.execLib(this);
                        this.type = ttemp;
                    } else if( (proc = (Node)WorkSpace.getProc(val)) != null ) {
                        // if procedure ...
                        if (child != null && proc.child[1] != null) {
                            // ... get declaration list and ...
                            Node decl = proc.child[1];
                            Node param = child[0];
                            param.exec();
                            // ... push parameter names onto stack ...
                            Stack id_list = new Stack();
                            id_list.push(decl.val);
                            while(decl.child[0] != null) {
                                decl = decl.child[0];
                                id_list.push(decl.val);
                            }
                            while(id_list.size() > 0) {
                                // ... then assgin parameter values to names
                                WorkSpace.putVar(id_list.pop(), WorkSpace.stack.pop());
                            }
                        }
                        proc.exec();
                        returnVal = WorkSpace.getVar(val);
                        WorkSpace.delVar(val); // after return delete
                    } else if( WorkSpace.symTab.containsKey(val.toString().toUpperCase()) == true ) {
                        // otherwise if it's a variable ...
                        if (child == null) {
                            WorkSpace.error("[ERROR] UNKOWN: " + val + "()"); WorkSpace.contFlag = false; WorkSpace.breakFlag = true;
                            return null;
                        }
                        child[0].exec();
                        // ... sure it's an array ...
                        arr = (Vector)WorkSpace.symTab.get(val.toString().toUpperCase());
                        int idx = MicroDouble.intValue(((Value)WorkSpace.stack.pop()).dVal);
                        // ... get array element ...
                        if (arr.size() > idx-1 && arr.elementAt(idx-1) != null) {
                            returnVal = (Value)arr.elementAt(idx-1);
                        } else { returnVal = new Value(MicroDouble.ZERO); }
                        // ... else return zero
                    } else { returnVal = new Value(MicroDouble.ZERO); }
                    break;

                case Parser.SUB:
                    child[0].exec();
                    // after execution, reset falgs
                    WorkSpace.breakFlag = false;
                    WorkSpace.contFlag = false;
                    break;

                case Parser.OPEN:
                    WorkSpace.openFile(child[0].exec().sVal, val.toString());
                    break;

                case Parser.ERROR:
                    ErrorNode er = (ErrorNode)child[0];
                    WorkSpace.error(er.row, er.col, " @ " + er.row + " " + er.col + "\n");
                    System.exit(1);
                default:
                    break;
            }
        } catch( Exception e) {
            throw new Error(e.toString() + "@ "+child[0].line+":"+child[0].col);
        }
        return returnVal;
    } ;
}
